package me.ingxin.android.router.compiler.processor

import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.TypeSpec
import me.ingxin.android.router.annotaions.Route
import me.ingxin.android.router.annotaions.compiler.LRoute
import me.ingxin.android.router.annotaions.compiler.LRouteIndex
import me.ingxin.android.router.compiler.util.Logger
import me.ingxin.android.router.compiler.util.ProcessorUtil
import me.ingxin.android.router.compiler.util.ROUTE_INDEX_NAME_PREFIX
import me.ingxin.android.router.compiler.util.ROUTE_INDEX_PACKAGE_NAME
import java.util.*
import javax.annotation.processing.RoundEnvironment
import javax.lang.model.element.AnnotationValue
import javax.lang.model.element.Modifier
import javax.lang.model.element.TypeElement
import javax.lang.model.util.ElementFilter

/**
 * Created by ingxin on 2022/4/14
 */
class RouteProcessor(processorUtil: ProcessorUtil, logger: Logger) :
    BaseProcessor(processorUtil, logger) {

    override fun getSupportedAnnotationTypes(): MutableSet<String> {
        return HashSet<String>().apply { add(Route::class.java.name) }
    }

    /**
     * 解析[Route]并写入路由中间表
     */
    fun process(env: RoundEnvironment) {
        val annotatedElements = env.getElementsAnnotatedWith(Route::class.java) ?: return
        //获取所有使用了@Route的类
        val routeList = ElementFilter.typesIn(annotatedElements) ?: return
        if (routeList.isNotEmpty()) {
            writeRouteIndex(routeList)
        }
    }

    private fun writeRouteIndex(routes: Set<TypeElement>) {
        //生成的路由表注解唯一id
        val routeIndexIdBuilder = StringBuilder()
        //路由信息代码块
        val cbb = CodeBlock.builder()
            .add("[\n")

        val routeMap = HashMap<String, LRoute>()
        for ((index, item) in routes.withIndex()) {
            val route = buildRoute(routeMap, item)

            //path
            cbb.add("%T(\"${route.path}\"", LRoute::class).add(",·")

            //interceptorName
            cbb.add("[")
            for ((i, e) in route.interceptorName.withIndex()) {
                cbb.add("\"$e\"")
                if (i != route.interceptorName.size - 1) {
                    cbb.add(",·")
                }
            }
            cbb.add("]").add(",·")

            //interceptor
            cbb.add("[")
            for ((i, e) in route.interceptor.withIndex()) {
                cbb.add("\"$e\"")
                if (i != route.interceptor.size - 1) {
                    cbb.add(",·")
                }
            }
            cbb.add("]").add(",·")

            //hold
            cbb.add("${route.hold}").add(",·")

            //destination
            cbb.add("\"${route.destination}\"")
                .add(")")

            if (index != routes.size - 1) {
                cbb.add(",")
            }
            cbb.add("\n")

            //拼接id
            routeIndexIdBuilder.append(route.destination.replace(".", "_")).append("_")
        }
        routeMap.clear()
        cbb.add("]")

        //生成路由表注解
        val annotationSpec = AnnotationSpec.builder(LRouteIndex::class)
            .addMember(cbb.build())
            .build()

        //将唯一id转化成uuid的形式
        val routeIndexId = routeIndexIdBuilder.substring(0, routeIndexIdBuilder.length - 1)
        val routeIndexName = "${ROUTE_INDEX_NAME_PREFIX}${
            UUID.nameUUIDFromBytes(routeIndexId.toByteArray()).toString().replace("-", "_")
        }"

        //生成路由表类
        val classSpec = TypeSpec.classBuilder(routeIndexName)
            .addAnnotation(annotationSpec)
            .build()

        //创建类文件
        processorUtil.writeClass(ROUTE_INDEX_PACKAGE_NAME, classSpec)

    }

    /**
     * 构建路由信息
     */
    private fun buildRoute(
        routeMap: HashMap<String, LRoute>,
        item: TypeElement
    ): LRoute {

        //获取路由信息
        val annotation = item.getAnnotation(Route::class.java)
        val className = item.qualifiedName.toString()
        val path = annotation.path

        if (!item.modifiers.contains(Modifier.PUBLIC)) {
            logger.throwException("[$className] modifier must be public!")
        }

        //检测路由合法性
        if (path.isBlank() || path.isEmpty()) {
            logger.throwException("[$className] route path is empty!")
        }

        //路由必须已"/"开头
        if (!path.startsWith("/")) {
            logger.throwException("[$className], the route path must be start with '/'!")
        }

        //检测路由是否存在
        if (routeMap[path] != null) {
            logger.throwException("Duplicate route path [$path] found in class [${routeMap[path]?.destination}] and class [$className]")
        }

        return LRoute(
            annotation.path,
            annotation.interceptorName,
            getInterceptorClassName(item).toTypedArray(),
            annotation.hold,
            className
        ).also {
            routeMap[path] = it
        }
    }

    /**
     * 获取[Route.interceptor]的className字段
     */
    private fun getInterceptorClassName(item: TypeElement): ArrayList<String> {
        val res = ArrayList<String>()
        val routeType =
            processorUtil.env.elementUtils.getTypeElement(Route::class.java.name).asType()
        for (am in item.annotationMirrors) {
            val map = am.elementValues
            if (am.annotationType.toString() != routeType.toString()) {
                continue
            }
            for (key in map.keys) {
                //找到Route#interceptor字段
                if (key.simpleName.toString() == "interceptor") {
                    val each = map[key]?.value as? Collection<*> ?: continue
                    for (e in each) {
                        val type = (e as? AnnotationValue)?.value ?: continue
                        res.add(type.toString())
                    }
                    break
                }
            }
        }
        return res
    }


    /**
     * 解析当前module[Route]并返回路由表
     */
    fun getRoute(env: RoundEnvironment): MutableMap<String, LRoute>? {
        val annotatedElements = env.getElementsAnnotatedWith(Route::class.java) ?: return null
        val routeList = ElementFilter.typesIn(annotatedElements) ?: return null
        val routeMap = HashMap<String, LRoute>()
        routeList.forEach { buildRoute(routeMap, it) }
        return routeMap
    }

}